package org.jeecg.common.util.list2tree;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.Random;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import static com.alibaba.fastjson.JSON.toJSON;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;

/**
 * @author addzero
 */
@Slf4j
@SuppressWarnings("all")
public class ListToTreeUtil {

    public static void main(String[] args) {

        final List<Node> nodeList = createRandomTestTreeInfoList(2, 2, 2);
        System.out.println(JSON.toJSONString(nodeList));
        //这里没指定root节点的判断方式
//       第二参数Predicate传入 e->e.getpid==0
        final List<Node> nodes = ListToTreeUtil.list2Tree(nodeList, Node::getId, Node::getPid, Node::getChildren, Node::setChildren);
        System.out.println("=========================================");
        System.out.println(toJSON(nodes));
    }

    public static List<Node> createRandomTestTreeInfoList(int tierN, int rootN, int childN) {
        final Random random = new Random();

        final int tier = tierN == 0 ? 5 + random.nextInt(5) : tierN;
        final List<Node> testTrees = new ArrayList<>();

        Queue<Node> pre = new LinkedList<>();

        final int rootNum = rootN == 0 ? 1 + random.nextInt(5) : rootN;

        for (int i = 0; i < rootNum; i++) {
            final Node testTree = new Node().setId(testTrees.size());
            testTrees.add(testTree);
            pre.add(testTree);
        }
        for (int i = 0; i < tier; i++) {
            final int childNum = childN == 0 ? 3 + random.nextInt(3) : childN;

            final int size = pre.size();

            for (int j = 0; j < size; j++) {
                final Integer pid = Objects.requireNonNull(pre.poll()).getId();
                for (int k = 0; k < childNum; k++) {
                    final Node child = new Node().setId(testTrees.size()).setPid(pid);
                    testTrees.add(child);
                    pre.offer(child);
                }
            }
        }
        return testTrees;
    }

    public static <T> List<T> list2Tree(List<T> source, XKFunction<T, ?> idFun, XKFunction<T, ?> pidFun
        , XKFunction<T, List<T>> getChildFun, XKBiConsumer<T, List<T>> setChildFun) {
        return list2Tree(source, null, idFun, pidFun, getChildFun, setChildFun);
    }

    /**
     * 支持任意实体 List转Tree
     *
     * @param source      源对象
     * @param isRoot      根节点判断e->e.getPid==null或者e->e.getPid==0
     * @param idFun       T::getId
     * @param pidFun      T::getPId
     * @param getChildFun T::getChildren
     * @param setChildFun T::setChildren
     * @param <T>
     * @return
     */
    public static <T> List<T> list2Tree(List<T> source, XKPredicate<T> isRoot, XKFunction<T, ?> idFun
        , XKFunction<T, ?> pidFun, XKFunction<T, List<T>> getChildFun, XKBiConsumer<T, List<T>> setChildFun) {
        if (Objects.isNull(source) || Objects.isNull(idFun) || Objects.isNull(pidFun)
            || Objects.isNull(getChildFun) || Objects.isNull(setChildFun) || source.isEmpty()) {
            log.info("参数不满足,直接返回空List");
            return new ArrayList<>();
        }
//        if (log.isInfoEnabled()) {
        log.info("isRoot->{},idFun->{},pidFun->{},getChildFun->{},setChildFun->{}", getMethodName(isRoot)
            , getMethodName(idFun), getMethodName(pidFun), getMethodName(getChildFun), getMethodName(setChildFun));
//        }
        if (log.isDebugEnabled()) {
            log.debug("source->{}", JSON.toJSONString(source));
        }
        final List<T> ret = new ArrayList<>();
        final Map<Object, T> map = new HashMap<>();

        source.forEach(t -> {
            Optional.ofNullable(isRoot).map(r -> {
                if (isRoot.test(t)) {
                    ret.add(t);
                }
                return r;
            }).orElseGet(() -> {
                Optional.ofNullable(pidFun.apply(t)).orElseGet(() -> {
                    ret.add(t);
                    return null;
                });
                return null;
            });
            map.put(idFun.apply(t), t);
        });

        source.forEach(t -> {
            map.computeIfPresent(pidFun.apply(t), (k, v) -> {
                final List<T> ts = Optional.ofNullable(getChildFun.apply(v)).orElseGet(() -> {
                    final List<T> list = new ArrayList<>();
                    setChildFun.accept(v, list);
                    return list;
                });
                ts.add(t);
                return v;
            });
        });
        if (log.isDebugEnabled()) {
            log.debug("返回数据->{}", JSON.toJSONString(ret));
        }

        System.out.println(JSON.toJSONString(ret));
        return ret;
    }

    private static <T> String getMethodName(GetLambdaName fun) {
        if (fun != null) {
            final String lambdaMethodName = fun.getLambdaMethodName();
            System.out.println("lambdaMethodName = " + lambdaMethodName);
            return lambdaMethodName;
        }
        return null;
    }

    /**
     * 树形数据转list
     *
     * @param treeMenu
     * @return
     */
    public static <T> List<T> tree2List(List<T> treeMenu, XKFunction<T, List<T>> getChildFun, XKBiConsumer<T, List<T>> setChildFun) {

        List<T> listMenu = new ArrayList<>();
        for (T node : treeMenu) {
            List<T> child = getChildFun.apply(node);
            listMenu.add(node);
            if (CollUtil.isNotEmpty(child)) {
                listMenu.addAll(tree2List(child, getChildFun, setChildFun));
                setChildFun.accept(node, null);
            }
        }
        return listMenu;
    }

    public static <T, R> List<JSONObject> buildTree(List<T> list, Map<String, Function<T, ?>> groupByFunctions, Function<T, R> reMappingFunction) {
        TreeConfig[] treeConfigs = groupByFunctions.entrySet().stream().map(e -> {
            String nodeName = e.getKey();
            Function<T, ?> value = e.getValue();
            Comparator<? super T> comparing = Comparator.comparing(Object::toString);
            return new TreeConfig<T>() {{
                setGroupFunction(value);
                setSortFunction(comparing);
                setNodeName(nodeName);
            }};
        }).toArray(TreeConfig[]::new);
        return buildTree(list, treeConfigs, reMappingFunction);
    }

    public static <T, R> List<JSONObject> buildTree(List<T> list, TreeConfig<T>[] treeConfigs, Function<T, R> reMappingFunction) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }

        TreeConfig<T> firstConfig = treeConfigs[0];
        Comparator<? super T> firstConfigSortFunction = firstConfig.getSortFunction();
        Function<T, ?> firstGroupByFunction = firstConfig.getGroupFunction();

        return list.stream()
            .sorted(firstConfigSortFunction)
            .collect(groupingBy(firstGroupByFunction, LinkedHashMap::new, toList()))
            .entrySet().stream()
            .map(grouped -> {
                JSONObject node = new JSONObject(new LinkedHashMap<>());
                Object key = grouped.getKey();
                String nodeName = firstConfig.getNodeName();
                node.put(nodeName, key);

                if (treeConfigs.length > 1) {
                    TreeConfig<T>[] nextConfigs = Arrays.stream(treeConfigs).skip(1).toArray(TreeConfig[]::new);
                    List<JSONObject> children = buildTree(grouped.getValue(), nextConfigs, reMappingFunction);
                    node.put("children", children);
                } else {
                    R apply = reMappingFunction.apply(grouped.getValue().get(0));
                    node.put("data", apply);
                }

                return node;
            })
            .collect(Collectors.toList());
    }

    public interface GetLambdaName extends Serializable {
        String METHOD_NAME = "writeReplace";

        default String getLambdaMethodName() {
            final Class<? extends GetLambdaName> aClass = this.getClass();
            String implMethodName = null;
            try {
                final Method method = aClass.getDeclaredMethod(METHOD_NAME);
                method.setAccessible(true);
                SerializedLambda lambda = (SerializedLambda) method.invoke(this);
                implMethodName = lambda.getImplMethodName();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return implMethodName;
        }
    }

    @FunctionalInterface
    public interface XKFunction<T, R> extends Function<T, R>, GetLambdaName {
    }

    @FunctionalInterface
    public interface XKBiConsumer<T, R> extends BiConsumer<T, R>, GetLambdaName {
    }

    @FunctionalInterface
    public interface XKPredicate<T> extends Predicate<T>, GetLambdaName {
    }

    @Data
    @Accessors(chain = true)
    public static class Node {
        Integer id;

        Integer pid;

        List<Node> children;
    }

}
